/*
 * Copyright (C) 2009,2010,2011  Paulo Cesar Pereira de Andrade.
 *
 * This is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * Authors:
 *	Paulo Cesar Pereira de Andrade
 */

#include "exl.h"

#include <getopt.h>
#include <stdio.h>

/*
 * Prototypes
 */
static int
cfg_parse_options(int argc, char *argv[]);

static void
cfg_optimize(efunction_t *function, eint32_t pass, ebool_t loop);

static void
cfg_emit(efunction_t *function);

static void
cfg_methods(void);

static void
cfg_finish(void);

static int
cfg_compare_symbol_offset(const void *left, const void *right);

static void
cfg_rename_symbol(efunction_t *function, esymbol_t *old, esymbol_t *atom);

static void
cfg_map_symbol_use(efunction_t *function, esymbol_t *atom);

static void
cfg_remove_symbol(efunction_t *function, esymbol_t *atom);

static ebool_t
cfg_symbols(efunction_t *function);

static void
cfg_finish_check_literals(efunction_t *function, eint32_t *array);

static void
cfg_finish_literals(eint32_t *array);

/*
 * Initialization
 */
east_node_t		*cfg_note;
eint32_t		 cfg_pass;
eint32_t		 cfg_optsize;
eint32_t		 cfg_optlevel = 9;
eint32_t		 cfg_verbose;
eint32_t		 cfg_float_format;
mp_prec_t		 cfg_mpfr_prc;
mp_rnd_t		 cfg_mpfr_rnd;
char			*cfg_progname;
east_node_t		*cfg_nil_node;
sigjmp_buf		 cfg_env;

static ehash_t		*int_hash;
static east_node_t	*int_node;
static ehash_t		*float_hash;
static east_node_t	*float_node;
static ehash_t		*literal_hash;
static east_node_t	*literal_node;
static ehash_t		*functions;
static evector_t	*remove_vector;
static evector_t	*rename_vector;
static evector_t	*cfg_root;

/*
 * Implementation
 */
int
ecfg_main(int argc, char *argv[])
{
    int		 optind;
    estream_t	*stream;
    evector_t	*vector;

    optind = cfg_parse_options(argc, argv);

    gc_disable = 1;
    init_object();
    init_hash();
    init_string();
    init_stream();
    init_symbol();
#if HAVE_THREAD
    init_thread();
#endif
    init_parser();
    init_read();
    init_code();
    init_note();
    init_tag();
    init_cfg();
#if XWINDOW
    init_window();
#endif
    init_write();
    init_hashtable();
#if GC_DEBUG
    gc_debug = 1;
#endif
    gc_disable = 0;

    if (optind < argc) {
	int	index = argc - 1;
	do {
	    vector = eget_string((euint8_t *)argv[index],
				 strlen(argv[index]));
	    efopen(&tmain->obj, vector, S_read | S_source);
	    if ((stream = tmain->obj) == null)
		eerror("cannot read '%s'", argv[index]);
	    epush_input(stream);
	} while (--index >= optind);
    }
    else
	epush_input(std_input);
    ecode();

    finish_code();

    return (0);
}

void
init_cfg(void)
{
    eobject_t	*root;

    evector(&cfg_root, t_void, 10, 1);
    eadd_root((eobject_t *)&cfg_root);
    root = cfg_root->v.ptr;

    enew_hash((eobject_t *)&functions, H_pointer, 5);
    root[0] = functions;

    /* integers */
    enew_hash((eobject_t *)&int_hash, H_string, 5);
    root[1] = int_hash;
    enew(&int_node, code_immediate);
    root[2] = int_node;
    int_node->code = eop_int;

    /* floats */
    enew_hash((eobject_t *)&float_hash, H_string, 5);
    root[3] = float_hash;
    enew(&float_node, code_immediate);
    root[4] = float_node;
    float_node->code = eop_float;

    /* literals */
    enew_hash((eobject_t *)&literal_hash, H_string, 5);
    root[5] = literal_hash;
    enew(&literal_node, code_immediate);
    root[6] = literal_node;
    literal_node->code = eop_ll;

    enew(&cfg_nil_node, code_immediate);
    root[7] = cfg_nil_node;
    cfg_nil_node->code = eop_nil;

    evector(&remove_vector, t_void, 8, 1);
    root[8] = remove_vector;

    evector(&rename_vector, t_void, 8, 1);
    root[9] = rename_vector;
}

void
ecfg(void)
{
    eint32_t		 count;
    eentry_t		*entry;
    eint32_t		 offset;
    efunction_t		*lambda;
    erecord_t		*record;
    esymbol_t		*symbol;

    /* build once hash table of all functions */
    enew(&entry, entry);
    entry->name = fn_root;
    eput_hash(functions, entry);
    for (offset = 1; offset < ecs_idx; offset++) {
	symbol = ecs[offset];
	lambda = symbol->value;
	if (lambda->defined) {
	    enew(&entry, entry);
	    entry->name = lambda;
	    eput_hash(functions, entry);
	}
    }
    for (offset = 0; offset < ets_idx; offset++) {
	record = record_vector[offset];
	if (record->methods && (count = record->methods->count)) {
	    for (--count; count >= 0; --count) {
		symbol = ets[offset].methods[count].code;
		lambda = symbol->value;
		if (lambda->defined && !eget_hash(functions, lambda)) {
		    enew(&entry, entry);
		    entry->name = lambda;
		    eput_hash(functions, entry);
		}
	    }
	}
    }

    init_ssa();
    init_cse();
    init_dfa();
    init_infer();

    cfg_optimize(fn_root, 0, false);
    if (cfg_optlevel) {
	for (offset = 0; offset < functions->size; offset++)
	    for (entry = functions->entries[offset]; entry; entry = entry->next)
		entry->value = null;
	cfg_optimize(fn_root, 1, true);
    }
    cfg_finish();

    finish_infer();
    finish_dfa();
    finish_cse();
    finish_ssa();
}

east_node_t *
ecode_int(eint_t value)
{
    east_node_t				*node;
    eentry_t				*entry;

    immediate_int(int_node)		= value;
    if ((entry = eget_hash(int_hash, int_node)) == null) {
	v_check(1);
	enew(&node, code_immediate);
	node->code			= eop_int;
	immediate_int(node)		= value;
	v_push(node);
	enew(&entry, entry);
	entry->name = entry->value	= node;
	eput_hash(int_hash, entry);
	v_dec();
    }

    return (entry->value);
}

east_node_t *
ecode_float(efloat_t value)
{
    east_node_t				*node;
    eentry_t				*entry;

    immediate_float(float_node)		= value;
    if ((entry = eget_hash(float_hash, float_node)) == null) {
	v_check(1);
	enew(&node, code_immediate);
	node->code			= eop_float;
	immediate_float(node)		= value;
	v_push(node);
	enew(&entry, entry);
	entry->name = entry->value	= node;
	eput_hash(float_hash, entry);
	v_dec();
    }

    return (entry->value);
}

east_node_t *
ecode_literal(eint32_t offset)
{
    east_node_t				*node;
    eentry_t				*entry;

    immediate_offset(literal_node)	= offset;
    if ((entry = eget_hash(literal_hash, literal_node)) == null) {
	v_check(1);
	enew(&node, code_immediate);
	node->code			= eop_ll;
	immediate_offset(node)		= offset;
	v_push(node);
	enew(&entry, entry);
	entry->name = entry->value	= node;
	eput_hash(literal_hash, entry);
	v_dec();
    }

    return (entry->value);
}

east_node_t *
ecode_switch(void)
{
    east_node_t				*node;
    eentry_t				*entry;

    v_check(1);
    enew(&node, code_immediate);
    node->code				= eop_jh;
    v_push(node);
    enew(&entry, entry);
    entry->name = entry->value		= node;
    eput_hash(literal_hash, entry);
    v_dec();

    return (node);
}

ebool_t
ecfg_implicit_used_p(east_list_t *list)
{
    for (; list; list = east_next(list)) {
	switch (east_node(list)->code) {
		/* modify implicit value without need of previous one */
	    case eop_nil:			case eop_int:
	    case eop_float:			case eop_ll:
	    case eop_ld:			case eop_ldt:
	    case eop_pd:			case eop_pdt:
	    case eop_lb:			case eop_lbt:
	    case eop_pb:			case eop_pbt:
	    case eop_lc:			case eop_ls:
	    case eop_pop:			case eop_call:
	    case eop_blt:			case eop_anon:
	    case eop_new:			case eop_vnewi:
	    case eop_lvai:			case eop_try:
	    case eop_exit:
		return (false);

		/* don't modify implicit value */
	    case eop_note:			case eop_label:
	    case eop_block:			case eop_enter:
	    case eop_begin:			case eop_vas:
	    case eop_val:			case eop_unwind:
		break;

	    case eop_ret:			case eop_reti:
		/* allow omitting computation of implicit value */
		if (fn_current->name && fn_current->name->tag == void_tag)
		    return (false);

		/* use implicit value */
	    default:
		return (true);
	}
    }
    /* invalid argument */
    abort();
}

static int
cfg_parse_options(int argc, char *argv[])
{
    static char		*short_options = "O::v::";
    static struct option long_options[] = {
	{ "help",		0, 0, 'h' },
	{ "fdefault-float",	1, 0, 'f' },
	{ "fmpfr-prec",		1, 0, 'p' },
	{ 0,			0, 0, 0   }
    };

    int 	 error;
    int		 opt_index;
    int		 opt_short;
    char	*endptr;

    cfg_progname = argv[0];
    cfg_mpfr_prc = mpfr_get_default_prec();
    cfg_mpfr_rnd = mpfr_get_default_rounding_mode();

    for (error = 0; !error;) {
	opt_short = getopt_long_only(argc, argv, short_options,
				     long_options, &opt_index);
	if (opt_short < 0)
	    break;
	switch (opt_short) {
	    case 'h':
	    default:
	    fail:
		fprintf(stderr,"\
Options:\n\
  -help                    Display this information\n\
  -O[0-n]                  Number of optimization passes\n\
  -Os                      Optimize for size\n\
  -v[0-3]                  Verbose output level\n\
  -fdefault-float={double|mpfr}\n\
                           Default float format\n\
  -fmpfr-prec={2-n}        Mpfr precision\n");
		error = 1;
		break;
	    case 'O':
		if (optarg) {
		    if (optarg[0] == 's' && optarg[1] == '\0')
			cfg_optsize = 1;
		    else {
			cfg_optlevel = strtol(optarg, &endptr, 10);
			if (*endptr || cfg_optlevel < 0)
			    goto fail;
		    }
		}
		else
		    cfg_optlevel = 3;
		break;
	    case 'v':
		if (optarg) {
		    cfg_verbose = strtol(optarg, &endptr, 10);
		    if (*endptr || cfg_verbose < 0)
			goto fail;
		}
		else
		    cfg_verbose = 1;
		break;
	    case 'f':
		if (strcmp(optarg, "mpfr") == 0)
		    cfg_float_format = 1;
		else if (strcmp(optarg, "double"))
		    goto fail;
		break;
	    case 'p':
		cfg_mpfr_prc = strtol(optarg, &endptr, 10);
		if (*endptr ||
		    cfg_mpfr_prc < MPFR_PREC_MIN ||
		    cfg_mpfr_prc > MPFR_PREC_MAX)
		    goto fail;
		mpfr_set_default_prec(cfg_mpfr_prc);
		break;
	}
     }

    if (error)
	exit(error);

    return (optind);
}

static void
cfg_optimize(efunction_t *function, eint32_t pass, ebool_t loop)
{
    ehash_t	*hash;
    eentry_t	*entry;
    ebool_t	 change;
    eint32_t	 offset;
    esymbol_t	*symbol;

    /* if never defined */
    if (!function->defined)
	return;

    /* if already visited in this pass */
    entry = eget_hash(functions, function);
    if (entry->value)
	return;
    entry->value = efixnum(1);

    fn_current = function;

    cfg_pass = pass;
    do {
	change = false;

	/* basic optimization */
	if (eoptimize(function))
	    change = true;

	/* adjust basic blocks represented by labels and set ssa counter */
	essa(function);

	/* constant/copy propagation */
	if (epropagate(function))
	    change = true;

	/* basic live variable and expression analysis */
	if (elive(function))
	    change = true;

	/* fold constant expressions */
	if (efold(function))
	    change = true;

	/* strength reduce and simplify expressions */
	if (ereduce(function))
	    change = true;

	/* eliminate common subexpressions */
	if (ecse(function))
	    change = true;

	/* remove unused cse temporaries and local variables */
	if (cfg_symbols(function))
	    change = true;

#if 0	/* until functional and able to do code changes... */
	/* data flow analysis */
	edfa(function);
#endif
    } while (loop && change && cfg_pass++ < cfg_optlevel);

    /* FIXME virtual methods are not properly added to the root function
     * hash table of called functions, so, add them all explicitly */
    if (function == fn_root)
	cfg_methods();

    hash = function->functions;
    for (offset = 0; offset < hash->size; offset++) {
	for (entry = hash->entries[offset]; entry; entry = entry->next) {
	    symbol = entry->name;
	    if (!symbol->a_builtin)
		cfg_optimize(symbol->value, pass, loop);
	}
    }
}

static void
cfg_methods(void)
{
    eint32_t	 i;
    eint32_t	 j;
    eint32_t	 count;
    eentry_t	*entry;
    eclass_t	*record;
    esymbol_t	*symbol;

    for (i = 0; i < ets_idx; i++) {
	record = record_vector[i];
	if (record->methods && record->methods->count) {
	    count = record->methods->count;
	    for (j = 0; j < count; j++) {
		symbol = ets[i].methods[j].code;
		if (symbol->value && !eget_hash(fn_root->functions, symbol)) {
		    enew(&entry, entry);
		    entry->name = symbol;
		    eput_hash(fn_root->functions, entry);
		}
	    }
	}
    }
}

static void
cfg_emit(efunction_t *function)
{
    if (function->defined) {
	fn_current = function;
	eemit(function);
    }
}

static void
cfg_finish(void)
{
    eint32_t	 i;
    eint32_t	*array;
    eentry_t	*entry;
    efunction_t	*function;
    v_enter();

    v_check(1);
    enew_object((eobject_t *)&array, t_void,
		((els_idx / 32) + 1) * sizeof(eint32_t));
    v_push(array);

    init_note();
    init_emit();

    /* final cleanup pass before code generation */
    for (i = 0; i < functions->size; i++) {
	for (entry = functions->entries[i]; entry; entry = entry->next) {

	    /* if function not referenced */
	    if (entry->value == null)
		continue;

	    fn_current = entry->name;

#if 1	    /* until functional and able to do code changes... */
	    /* data flow analysis */
	    edfa(fn_current);
#endif

	    /* implicit type information */
	    einfer(fn_current);

	    (void)ecode_enter_finish(fn_current);

	    /* check literals that are actually used */
	    cfg_finish_check_literals(fn_current, array);
	}
    }

    cfg_finish_literals(array);
    v_leave();

    /* generate code, with fn_root first; actually, should use declaration
     * order, that should reduce number or forward references (but parsing
     * would be even more important to use declaration order) */
    cfg_emit(fn_root);
    erem_hash(functions, eget_hash(functions, fn_root));
    for (i = 0; i < functions->size; i++) {
	for (entry = functions->entries[i]; entry; entry = entry->next)
	    if (entry->value)
		cfg_emit(entry->name);
    }

    finish_emit();

    /* drop no longer required information */

    /* root function hash used to check for unbalanced stack */
#if DEBUG
    eclr_hash(fn_root->hash);
#else
    fn_root->hash =
#endif
	fn_root->functions = fn_root->hsym = null;
    fn_root->labels = fn_root->vsym =
	fn_root->loads = fn_root->stores = null;
    fn_root->code = null;
    for (i = 0; i < functions->size; i++) {
	for (entry = functions->entries[i]; entry; entry = entry->next) {
	    function = entry->name;
	    function->hash = function->functions = function->hsym = null;
	    function->labels = function->vsym =
		function->loads = function->stores = null;
	    function->code = null;
	}
    }

    /* release all cfg memory */
    erem_root((eobject_t *)&cfg_root);

    finish_note();
}

static int
cfg_compare_symbol_offset(const void *left, const void *right)
{
    return ((*(esymbol_t **)left)->offset - (*(esymbol_t **)right)->offset);
}

static void
cfg_rename_symbol(efunction_t *function, esymbol_t *old, esymbol_t *atom)
{
    eint32_t		  bit;
    essa_load_data_t	 *data;
    essa_load_t		 *load;
    essa_load_t		**loads;
    eentry_t		 *entry;
    essa_store_t	 *store;
    essa_store_t	**stores;
    eint32_t		  offset;

    assert(old->type == atom->type &&
	   !old->a_global && !atom->a_global &&
	   !old->a_field  && !atom->a_field);

    entry = eget_hash(function->hsym, old);
    assert(entry != null);
    bit = eget_fixnum(entry->value);

    /* update store information */
    stores = function->stores->v.obj;
    store = stores[bit];
    for (offset = store->bot; offset <= store->top; offset++) {
	if (store->data[offset].list)
	    east_node(store->data[offset].list)->csymbol.symbol = atom;
    }

    /* update load information */
    loads = function->loads->v.obj;
    load = loads[bit];
    for (offset = load->bot; offset <= load->top; offset++) {
	data = load->data + offset;
	if (data->node)
	    data->node->csymbol.symbol = atom;
    }
}

#define zmsk			thr_zr
static void
cfg_map_symbol_use(efunction_t *function, esymbol_t *atom)
{
    unsigned long	 bit;
    unsigned long	 bot;
    unsigned long	 top;
    eobject_t		*data;
    elabel_t		*temp;
    eentry_t		*entry;
    elabel_t		*label;
    essa_store_t	*store;
    eint32_t		 offset;

    /* get symbol offset in bit vectors */
    entry = eget_hash(function->hsym, atom);
    assert(entry != null);
    bit = eget_fixnum(entry->value);

    /* get first basic block setting symbol */
    label = null;
    data = function->stores->v.obj;
    store = data[bit];
    for (offset = store->bot; offset <= store->top; offset++) {
	if ((temp = store->data[offset].label) &&
	    (label == null || label->ident > temp->ident))
	    label = temp;
    }
    if (label == null)
	label = east_node(function->code)->clabel.label;

    /* set bit for every basic block using symbol */
    mpz_set_ui(zmsk, 0);
    for (; label; label = label->next) {
	if (mpz_tstbit(label->use, bit) ||
	    mpz_tstbit(label->phi, bit) ||
	    mpz_tstbit(label->set, bit))
	    mpz_setbit(zmsk, label->ident);
    }

    /* map bits to basic blocks where the symbol is live */
    bot = mpz_scan1(zmsk, 0);
    if ((bit = top = bot) != ULONG_MAX) {
	for (bit = mpz_scan1(zmsk, bit);
	     bit != ULONG_MAX; bit = mpz_scan1(zmsk, bit))
	    top = bit++;
	if (top != bot) {
	    mpz_set_ui(zmsk, 0);
	    mpz_setbit(zmsk, (top - bot) + 1);
	    mpz_sub_ui(zmsk, zmsk, 1);
	    mpz_mul_2exp(zmsk, zmsk, bot);
	}
	else
	    mpz_setbit(zmsk, top);
    }

    enew_mpz(&atom->value, zmsk);
}

static void
cfg_remove_symbol(efunction_t *function, esymbol_t *atom)
{
    ehash_t		*hash;
    esymbol_t		*next;
    esymbol_t		*prev;
    esymbol_t		*symbol;
    eint32_t		 offset;

    if (function->tryoff > 0 && function->tryoff > atom->offset)
	--function->tryoff;

    hash = function->hash;
    for (offset = 0; offset < hash->size; offset++) {
	symbol = prev = (esymbol_t *)hash->entries[offset];
	while (symbol) {
	    next = symbol->next;
	    if (symbol == atom) {
		/* bypass erem_hash() */
		if (symbol == prev) {
		    hash->entries[offset] = (eentry_t *)next;
		    symbol = prev = next;
		}
		else
		    prev->next = symbol = next;
		--hash->count;
		continue;
	    }

	    if (symbol->offset > atom->offset)
		--symbol->offset;
	    prev = symbol;
	    symbol = next;
	}
    }
    --hash->offset;
    --function->stack;
}

static ebool_t
cfg_symbols(efunction_t *function)
{
    empz_t		  z0;
    empz_t		  z1;
    eint32_t		  bit;
    esymbol_t		 *old;
    esymbol_t		 *atom;
    eobject_t		 *data;
    east_list_t		 *list;
    east_list_t		 *prev;
    ehash_t		 *hash;
    eentry_t		 *entry;
    eint32_t		  index;
    essa_store_t	 *store;
    ebool_t		  change;
    eint32_t		  offset;
    esymbol_t		**removes;
    esymbol_t		**renames;

    remove_vector->offset = 0;
    removes = remove_vector->v.obj;

    rename_vector->offset = 0;
    renames = rename_vector->v.obj;

    hash = function->hash;
    for (offset = 0; offset < hash->size; offset++) {
	atom = (esymbol_t *)hash->entries[offset];
	for (; atom; atom = atom->next) {

	    /* exception object should be kept even if never used */
	    if (atom->offset < 0 || atom->offset == function->tryoff)
		continue;

	    /* only access to symbol may be taking it's address */
	    if ((entry = eget_hash(function->hsym, atom))) {
		index = eget_fixnum(entry->value);
		if (mpz_tstbit(function->ptr, index))
		    continue;
		if (atom->a_expression) {
		    /* symbol is candidate for rename */
		    if (rename_vector->offset >= rename_vector->length) {
			erenew_vector(rename_vector, rename_vector->length + 8);
			renames = rename_vector->v.obj;
		    }
		    renames[rename_vector->offset++] = atom;
		    cfg_map_symbol_use(function, atom);
		}
		continue;
	    }

	    /* queue symbol for removal */
	    if (remove_vector->offset >= remove_vector->length) {
		erenew_vector(remove_vector, remove_vector->length + 8);
		removes = remove_vector->v.obj;
	    }
	    removes[remove_vector->offset++] = atom;
	}
    }

    change = false;
    if (rename_vector->offset) {
	qsort(renames, rename_vector->offset, sizeof(eobject_t),
	      cfg_compare_symbol_offset);
	for (offset = 0; offset < rename_vector->offset - 1; offset++) {
	    atom = renames[offset];
	    z0 = atom->value;
	    index = offset + 1;
	    while (index < rename_vector->offset) {
		old = renames[index];
		z1 = old->value;
		mpz_and(zmsk, z0, z1);

		/* if temporaries live range don't clash */
		if (mpz_sgn(zmsk) == 0) {

		    /* update live range of symbol and rename */
		    mpz_ior(z0, z0, z1);
		    cfg_rename_symbol(function, old, atom);
		    --rename_vector->offset;
		    if (index < rename_vector->offset)
			renames[index] = renames[rename_vector->offset];

		    /* remove renamed symbol */
		    erem_temp_symbol(function, old);
		    change = true;
		}
		else
		    ++index;
	    }
	}
	for (offset = 0; offset < rename_vector->offset - 1; offset++)
	    renames[offset]->value = null;
    }

    if (remove_vector->offset) {
	qsort(removes, remove_vector->offset, sizeof(eobject_t),
	      cfg_compare_symbol_offset);
	/* remove higher offset first */
	for (offset = remove_vector->offset - 1; offset >= 0; offset--) {
	    atom = removes[offset];

	    /* if the symbol is not used there should be no entry */
	    if ((entry = eget_hash(function->hsym, atom))) {
		bit = eget_fixnum(entry->value);
		data = function->stores->v.obj;
		if ((store = data[bit]) && store->bot) {
		    for (index = store->bot; index <= store->top; index++) {
			if ((list = store->data[index].list) == null)
			    continue;
			prev = elabel_list(store->data[index].label);
			for (; east_next(prev) != list; prev = east_next(prev))
			    ;
			east_next(prev) = east_next(list);
		    }
		    store->bot = store->top = 0;
		}
	    }
	    if (atom->a_expression)
		erem_temp_symbol(function, atom);
	    else
		cfg_remove_symbol(function, atom);
	}
	change = true;
    }

    return (change);
}
#undef zmsk

static void
cfg_finish_check_literals(efunction_t *function, eint32_t *array)
{
    east_node_t		*node;
    east_list_t		*list;
    eint32_t		 index;

    for (list = function->code; list; list = ecdr(list)) {
	node = east_node(list);
	switch (node->code) {
	    case eop_ll:			case eop_jh:
		index = immediate_offset(node);
		array[index / 32] |= 1 << (index & 31);
		break;
	    default:
		break;
	}
    }
}

static void
cfg_finish_literals(eint32_t *array)
{
    east_node_t		*node;
    eentry_t		*prev;
    eentry_t		*entry;
    eint32_t		 index;
    eint32_t		 value;
    eint32_t		 offset;

    for (offset = 0; offset < int_hash->size; offset++) {
	for (entry = int_hash->entries[offset]; entry; entry = entry->next) {
	    node = entry->value;
	    if (immediate_int(node) >  2147483647L ||
		immediate_int(node) < -2147483648L)
		node->code = eop_long;
	}
    }

    for (index = els_idx - 1; index >= 0; index--) {
	if (array[index / 32] & (1 << (index & 31)))
	    continue;

	/* constant was generated, but ended up being the operand of
	 * constant folding, or is no longer used due to dead code removal */
	for (offset = 0; offset < literal_hash->size; offset++) {
	    for (prev = entry = literal_hash->entries[offset]; entry;) {
		node = entry->value;
		value = immediate_offset(node);
		if (value == index) {
		    /* remove entry */
		    if (prev == entry)
			literal_hash->entries[offset] =
			    prev = entry = entry->next;
		    else
			prev->next = entry = entry->next;
		    --literal_hash->count;
		    continue;
		}
		else if (value > index)
		    /* update entry */
		    immediate_offset(node) = value - 1;

		/* update loop iteration */
		prev = entry;
		entry = entry->next;
	    }
	}

	/* remove from literal constant vector */
	memmove(els + index, els + index + 1,
		(els_idx - index) * sizeof(eobject_t));
	--els_idx;
    }
}
